/*==============================================================================
  File Name   : main.cpp

  Description : Entry point for UDP receiver developed by using Eclipse.

  Created on  : 2009-02-27
  Created by  : Shiou Ming, Lee

  Updated on  : 2009-02-27
  Updated by  : Shiou Ming, Lee
  Changes     : version 0.1.0.1
                - Created new module - UDP Receiver Demo.

  Cautions    : (important notes)
  ============================================================================*/


//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <ws2tcpip.h>  // included winsock2.h

#define _APP_VERSION_  _T("0.1.0.1")
#define _APP_NAME_     _T("UDP Receiver Demo")

#define _SCK_VERSION1_ 0x0101
#define _SCK_VERSION2_ 0x0202


//------------------------------------------------------------------------------
//-- TYPEDEF / STRUCT / UNION / ENUM
//------------------------------------------------------------------------------
struct ST_CMDLINE_OPTIONS
{
  bool bOptState_Help;
  bool bOptState_Version;
  bool bOptState_DebugMode;
};


//------------------------------------------------------------------------------
//-- GLOBAL
//------------------------------------------------------------------------------
ST_CMDLINE_OPTIONS g_stCmdLineOptions = {false, false, false};


//------------------------------------------------------------------------------
//-- FUNCTION PROTOTYPES
//------------------------------------------------------------------------------
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[]);
bool MatchCmdLineOpt(int _nKeywordCount,
                     const _TCHAR *_szKeywordList[],
                     const _TCHAR *_szCmdLineArg);

bool ReceiveUDP(unsigned short _nPortNum);
void PrintLastSockError(const _TCHAR *_szAppErrMsg);


//------------------------------------------------------------------------------
//-- MAIN FUNCTION
//------------------------------------------------------------------------------
int _tmain(int _argc, _TCHAR *_argv[], _TCHAR *_envp[])
{
  //-- Try to process parameters in separate function, don't mess up main()
  if (!HandleCmdLineOpt(_argc, _argv)) return 0;

  //-- Main logic
  if (_argc == 2)
  {
    // Assumed that 3 parameter inputs will be: server IP, server port, data
    ReceiveUDP(_ttoi(_argv[1]));
  }
  else
  {
    ReceiveUDP(9999);
  }

  //-- Stop the console screen before quit for displaying the output.
  _tprintf(_T("\n\nPress <ENTER> key to exit"));
  _fgettchar();

  return 0;
}


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[])
{
  bool bContinueMainLogic = true;

  // Declare the list of valid keywords for various command line options
  const int KWORDCOUNT_VERSION = 3;
  const _TCHAR *ARG_KEYWORD_VERSION[KWORDCOUNT_VERSION]
      = {_T("--version"), _T("-v"), _T("-ver")};

  const int KWORDCOUNT_HELP = 3;
  const _TCHAR *ARG_KEYWORD_HELP[KWORDCOUNT_HELP]
      = {_T("--help"), _T("-h"), _T("?")};

  const int KWORDCOUNT_DEBUG = 2;
  const _TCHAR *ARG_KEYWORD_DEBUG[KWORDCOUNT_DEBUG]
      = {_T("--debug"), _T("-d")};

  // Loop through command line options and take necessary actions
  //_tprintf(_T("\n=== Command Line Parameters ===\n"));
  for (int i = 0; i < _argc; i++)
  {
    if ((!g_stCmdLineOptions.bOptState_Version) &&
        (MatchCmdLineOpt(KWORDCOUNT_VERSION, ARG_KEYWORD_VERSION, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Version = true;
      _tprintf(_T("\nVERSION INFO\n------------"));
      _tprintf(_T("\n%s version %s\n"), _APP_NAME_, _APP_VERSION_);
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_Help) &&
             (MatchCmdLineOpt(KWORDCOUNT_HELP, ARG_KEYWORD_HELP, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Help = true;
      _tprintf(_T("\nHELP INFO\n---------"));
      _tprintf(_T("\nUSAGE: UDPReceiver [<server port>]\n"));
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_DebugMode) &&
             (MatchCmdLineOpt(KWORDCOUNT_DEBUG, ARG_KEYWORD_DEBUG, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_DebugMode = true;
      _tprintf(_T("\nDEBUG MODE\n----------"));
      _tprintf(_T("\nRunning in debug mode!\n"));
    }
    else
    {
      //_tprintf(_T("\nParameter[%d]\t- %s\n"), i, _argv[i]);
    }
  }
  //_tprintf(_T("\n=== END OF PARAMETERS ===\n\n"));

  return bContinueMainLogic;
}


bool MatchCmdLineOpt(int _nKeywordCount,
                     const _TCHAR *_szKeywordList[],
                     const _TCHAR *_szCmdLineArg)
{
  // Take one command line option and compare with the list of valid keywords,
  // return true if found matching.
  for (int i = 0; i < _nKeywordCount; i++)
  {
    if (_tcscmp(_szKeywordList[i], _szCmdLineArg) == 0) return true;
  }

  return false;
}


// _nPortNum   : Receive any UDP datagram that reach this port.
bool ReceiveUDP(unsigned short _nPortNum)
{
  SOCKET serverSocket;
  WSADATA wsaInfo;

  // Step 1: Init WinSock (for WinSock only, not necessary for Unix/Linux)
  int nError = WSAStartup(_SCK_VERSION2_, &wsaInfo);
  if (nError)
  {
    _tprintf(_T("\nError: WSAStartup() failed [%d]"), nError);
    return false;  // Check if WinSock init successful
  }

  // Verify if WinSock version matches with desired one (WinSock only)
  if (wsaInfo.wVersion != _SCK_VERSION2_)
  {
    _tprintf(_T("\nError: WinSock version unmatch."));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  // Step 2: Create the socket with socket() function
  //   Param 2: SOCK_STREAM = TCP; SOCK_DGRAM = UDP; ...
  //   Param 3: IPPROTO_IP (0); IPPROTO_TCP (6); IPPROTO_UDP(17); ...
  // Normally for TCP and UDP, param 3 (protocol) can be 0 (IPPROTO_IP) as well
  serverSocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (INVALID_SOCKET == serverSocket)
  {
    PrintLastSockError(_T("\nError socket()"));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  sockaddr_in serverAddress;
  memset(&serverAddress, 0, sizeof(serverAddress));
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_port = htons(_nPortNum);
  serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);

  // Step 4: Set socket option for broadcasting
  int nBroadcastPermission = 1;
  setsockopt(serverSocket, SOL_SOCKET, SO_BROADCAST,
             (_TCHAR *) &nBroadcastPermission, sizeof(nBroadcastPermission));

  // Step 4: Bind the socket to the address with bind() function
  // - The socket by default is in blocking mode, can be set to non-blocking
  //   mode with ioctlsocket() function BEFORE binding. Example:
  //     u_long ulArg = 1;
  //     ioctlsocket(serverSocket, FIONBIO, &ulArg);
  if (SOCKET_ERROR ==
        bind(serverSocket, (LPSOCKADDR)&serverAddress, sizeof(sockaddr_in)))
  {
    PrintLastSockError(_T("\nError bind()"));
    _tprintf(_T("\nAction: Close socket and clean up, then exit.\n"));
    closesocket(serverSocket);
    WSACleanup();
    return false;
  }

  // Step 5: Make the socket ready to receive UDP data with recvfrom() function.
  int recvBufferLen;
  const UINT BUFFER_SIZE = 32;
  _TCHAR szBuffer[BUFFER_SIZE] = {0};

  unsigned short nSourcePort = 0;
  _TCHAR * szSourceIP = new _TCHAR[INET_ADDRSTRLEN];
  sockaddr_in srcAddress;
  socklen_t srcAddressLen = sizeof(srcAddress);

  _tprintf(_T("Receiving datagram ...\t[port %d]\n"), _nPortNum);
  recvBufferLen = recvfrom(serverSocket, szBuffer, BUFFER_SIZE - 1, 0,
		  (sockaddr *) &srcAddress, &srcAddressLen);

  if (SOCKET_ERROR == recvBufferLen)
  {
    PrintLastSockError(_T("\nError socket recv()"));
  }
  else
  {
    szSourceIP = inet_ntoa(srcAddress.sin_addr);
    nSourcePort = ntohs(srcAddress.sin_port);
    _tprintf(_T("Received data\n  data\t\t\t[%s]\n"), szBuffer);
    _tprintf(_T("  from \t\t\t[%s:%d]\n"), szSourceIP, nSourcePort);

	_TCHAR *szReplyData = _T("some dummy data");
	_tprintf(_T("Replying ...\n  data\t\t\t[%s]\n"), szReplyData);
    sendto(serverSocket, szReplyData, _tcslen(szReplyData), 0,
           (sockaddr *) &srcAddress, sizeof(srcAddress));
  }

  // Remember to close the socket and do cleanup
  delete szSourceIP;
  szSourceIP = NULL;

  closesocket(serverSocket);
  WSACleanup();

  return true;
}


void PrintLastSockError(const _TCHAR *_szAppErrMsg)
{
#ifdef _WIN32
  int nLastSockErrCode = WSAGetLastError();
  _tprintf(_T("\n%s: [%d] %s\n"),
           _szAppErrMsg, nLastSockErrCode, gai_strerror(nLastSockErrCode));
#else
  _tperror(_szAppErrMsg);
#endif
}
